package com.zerro.litez.compiler.processor.parser;

import com.zerro.litez.annotations.dao.*;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.lang.annotation.Annotation;
import java.util.*;

public class DaoClassParser extends AbstractClassParser {

    /**
     * 所有被注解的方法
     */
    Map<ExecutableElement, AnnotationInfo> method_AnnotationMap = new HashMap<>();

    String entity;
    String entityPackage;
    String table;


    private static final Map<String, DaoClassParser> daoClassParserMap = new HashMap<>();

    public DaoClassParser(TypeElement dao) {
        super(dao);
        supportedAnnotation.add(Query.class);
        supportedAnnotation.add(Update.class);
        supportedAnnotation.add(Delete.class);
        supportedAnnotation.add(Insert.class);
        onParse();
        daoClassParserMap.put(dao.getSimpleName().toString(), this);

    }

    @Override
    protected void onParse() {
        setTable(parsingElement);
        checkMethods();
    }

    private void setTable(TypeElement dao) {
        entity = AnnotationInfo.getValueOf(dao.getAnnotation(Dao.class), "entity")
                .replaceFirst(".class", "");
        String[] s = entity.split("\\.");
        table = s[s.length - 1];
        entityPackage = entity.substring(0, entity.length() - table.length() - 1);
        if (EntityClassParser.getParser(entity) == null) {
            throw new RuntimeException("必须在 Dao <" + parsingElement + "> 的注解上声明实体类");
        }
    }

    private void checkMethods() {
        List<ExecutableElement> methods = getMethods(parsingElement, null);
        if (methods.isEmpty()) {
            throw new RuntimeException("这个 Dao <" + parsingElement + "> 没有任何方法");
        }
        for (ExecutableElement method : methods) {
            checkAnnotation(method);
            AnnotationInfo annotationInfo = method_AnnotationMap.get(method);
            if (annotationInfo.getName().equals(Query.class.getSimpleName())) {
                queryParamFormat(annotationInfo, method);
            } else {
                paramCheck(method);
            }
            returnCheck(method);
        }
    }

    private void checkAnnotation(ExecutableElement method) {
        List<Annotation> annoList = new ArrayList<>();
        for (Class<? extends Annotation> annotation : supportedAnnotation) {
            Annotation anno = method.getAnnotation(annotation);
            if (anno != null) {
                annoList.add(anno);
            }
            if (annoList.size() > 1) {   // 多个注解则报错
                throw new RuntimeException("<" + parsingElement + "> 里的方法 <"
                        + method.getSimpleName() + "> 具有多个注解");
            }
        }
        if (!annoList.isEmpty()) {
            method_AnnotationMap.put(method, new AnnotationInfo(annoList.get(0)));
        } else {   // 无注解则报错
            throw new RuntimeException("<" + parsingElement + "> 里的方法 <"
                    + method.getSimpleName() + "> 没有任何支持的注释");
        }
    }


    private void paramCheck(ExecutableElement method) {

        for (VariableElement param : method.getParameters()) {

            String typeStr = param.asType().toString();
            checkType(method, typeStr);
            if (!typeStr.contains(entity)) {
                throw new RuntimeException("参数必须是实体类或其数组或容器" + getAt(method, param));
            }

        }
    }

    Map<ExecutableElement, String> querySqlFormatMap = new HashMap<>();

    private void queryParamFormat(AnnotationInfo queryInfo, ExecutableElement method) {
        String sql = queryInfo.getValue("sql");
        if (sql.isEmpty()) {
            querySqlFormatMap.put(method, getSimpleQuery(method));
        } else {
            querySqlFormatMap.put(method, getWildQuery(method, sql));
        }

    }


    /**
     * 注解没有指定 sql 语句
     */
    private String getSimpleQuery(ExecutableElement method) {
        StringBuilder sql = new StringBuilder("select * from " + table);
        if (!method.getParameters().isEmpty()) {
            sql.append(" where ");
            List<? extends VariableElement> parameters = method.getParameters();
            String sign = " = ";
            for (int i = 0; i < parameters.size(); i++) {
                VariableElement parameter = parameters.get(i);
                if (parameter.getAnnotation(In.class) != null) {
                    sign = " like ";
                }
                String valueMap = getColumnName(method, parameter) + sign;
                if (parameter.asType().toString().equals(String.class.getName()) ||
                        parameter.asType().toString().contains("[]") ||
                        sign.equals(" like ")) {
                    valueMap += "'\" + $L + \"'";
                } else {
                    valueMap += "\" + $L + \"";
                }
                sql.append(valueMap);
                if (i != parameters.size() - 1) {
                    sql.append("and ");
                }
            }
        }
        return sql.toString();
    }


    private String getColumnName(ExecutableElement method, VariableElement param) {
        EntityClassParser entityClassParser = EntityClassParser.getParser(entity);
        FieldInfo fieldInfo = entityClassParser.getInfoOfField(param.getSimpleName().toString());

        if (fieldInfo == null) {
            throw new RuntimeException("错误的参数名称<" + param.getSimpleName() + ">，" +
                    "实体类<" + entity + ">中不存在这样命名的成员变量" + getAt(method, param));
        }
        String paramType = param.asType().toString();
        FieldInfo entityTypePk = fieldInfo.getEntityTypePk();
        if (param.getAnnotation(In.class) != null) {
            if (entityTypePk != null) {
                if (fieldInfo.isArrayType()) {
                    if (!entityTypePk.getEntityName().equals(paramType) &&
                            !entityTypePk.getJavaType().equals(paramType) &&
                            !(entityTypePk.getJavaType() + "[]").equals(paramType)) {
                        throw new RuntimeException("2错误的参数类型<" + paramType + ">，" +
                                "实体类<" + entity + ">中的<" + param.getSimpleName() + ">" +
                                "应是<" + entityTypePk.getJavaType() + ">类型"
                                + getAt(method, param));
                    }
                } else if (!entityTypePk.getJavaType().equals(paramType)) {
                    throw new RuntimeException("3错误的参数类型<" + paramType + ">，" +
                            "实体类<" + entity + ">中的<" + param.getSimpleName() + ">" +
                            "应是<" + entityTypePk.getJavaType() + ">类型"
                            + getAt(method, param));
                }
            } else if (fieldInfo.isArrayType() && !fieldInfo.getJavaType().equals(paramType + "[]")) {
                throw new RuntimeException("4错误的参数类型<" + paramType + ">，" +
                        "实体类<" + entity + ">中的<" + param.getSimpleName() + ">" +
                        "应是<" + fieldInfo.getJavaType() + ">类型"
                        + getAt(method, param));
            }
        } else {
            if (entityTypePk != null &&
                    !entityTypePk.getEntityName().equals(paramType) &&
                    !(entityTypePk.getEntityName() + "[]").equals(paramType)) {
                throw new RuntimeException("5错误的参数类型<" + paramType + ">，" +
                        "实体类<" + entity + ">中的<" + param.getSimpleName() + ">应是<" +
                        entityTypePk.getEntityName() + ">类型"
                        + getAt(method, param));
            }
            if ((fieldInfo.isSimpleType() && !fieldInfo.getJavaType().equals(paramType)) && !fieldInfo.isArrayType() ||
                    ((fieldInfo.isSimpleType() && !(fieldInfo.getJavaType()).equals(paramType)) && fieldInfo.isArrayType()) ||
                    (fieldInfo.isEnumType() && !formatEnumFullName(fieldInfo.getEnumFullName()).equals(paramType))) {
                throw new RuntimeException("6错误的参数类型<" + paramType + ">，" +
                        "实体类<" + entity + ">中的<" + param.getSimpleName() + ">应是<" + fieldInfo.getJavaType() + ">类型"
                        + getAt(method, param));
            }
        }

        return fieldInfo.getColumnName();
    }

    private String formatEnumFullName(String[] fullName) {
        return fullName[0] + "." + fullName[1];
    }

    /**
     * 获取使用 通配符 的 sql 语句
     */
    private String getWildQuery(ExecutableElement method, String sql) {
        char[] sqlCharArray = sql.toCharArray();
        StringBuilder sqlBuilder = new StringBuilder(sql);
        int formatCount = 0;
        boolean usingWild = false;
        boolean usingNamedWild = false;
        for (int i = 0, charArrayLength = sqlCharArray.length; i < charArrayLength; i++) {
            char c = sqlCharArray[i];
            if (c == '?') {
                formatCount++;
                if (i + 1 != sqlCharArray.length && sqlCharArray[i + 1] != ' ') {
                    usingNamedWild = true;
                    namedWildFormat(method, i, charArrayLength, sqlCharArray, sqlBuilder, formatCount);
                } else {

                    usingWild = true;
                    String replace = "\" + \\$L + \"";
                    VariableElement param = method.getParameters().get(formatCount - 1);
                    String type = param.asType().toString();

                    if (type.equals(String.class.getName())) {
                        replace = "'" + replace + "'";
                    }
                    sqlBuilder = new StringBuilder(sqlBuilder.toString()
                            .replaceFirst("\\?", replace));
                }

                sqlCharArray = sqlBuilder.toString().toCharArray();
                charArrayLength = sqlCharArray.length;
            }
            if (usingWild && usingNamedWild) {
                throw new RuntimeException("请不要混用 ? 通配符和 ?columnName"
                        + getAt(method, (String) null));
            }
        }
        return sqlBuilder.toString();
    }

    /**
     * 匹配参数化通配符
     *
     * @param method          方法
     * @param i               字符串替换起点
     * @param charArrayLength 匹配参数的名称长度
     * @param sqlCharArray    sql字符串的char数组
     * @param sqlBuilder      正在构建的sql语句
     */
    private void namedWildFormat(ExecutableElement method, int i, int charArrayLength,
                                 char[] sqlCharArray, StringBuilder sqlBuilder, int formatCount) {

        StringBuilder paramFormat = new StringBuilder();
        int j;
        for (j = i + 1; j < charArrayLength && sqlCharArray[j] != ' '; j++) {
            paramFormat.append(sqlCharArray[j]);
        }
        for (VariableElement parameter : method.getParameters()) {

            if (parameter.getSimpleName().toString().equals("L")) {
                throw new RuntimeException("使用参数通配符时， 请勿将 <L> 作为 参数名称,"
                        + getAt(method, parameter));
            }
            if (parameter.getSimpleName().toString().contentEquals(paramFormat)) {

                String replace;

                if (parameter.asType().toString().equals(String.class.getName())) {
                    replace = "'\" + $" + paramFormat + ":L + \"'";
                } else {
                    replace = "\" + $" + paramFormat + ":L";
                    if (formatCount != method.getParameters().size() - 1) {
                        replace += " + \"";
                    }
                }
                sqlBuilder.replace(i, j, replace);
                break;
            }
        }
    }

    private void returnCheck(ExecutableElement method) {
        String typeStr = method.getReturnType().toString();
        Query query = method.getAnnotation(Query.class);
        if (query != null) {
            if (query.sql().isEmpty() || query.sql().trim().startsWith("select")) {
                checkType(method, typeStr);
            }
            return;
        }
        if (!typeStr.equals(void.class.getName())) {
            throw new RuntimeException("不是 select 操作的方法只能返回 void"
                    + getAt(method, (String) null));
        }

    }

    private String getAt(ExecutableElement method, VariableElement param) {
        return getAt(method, param.getSimpleName().toString());
    }

    private void checkType(ExecutableElement method, String typeStr) {
        if (typeStr.equals(entity) || typeStr.equals(entity + "[]")) {
            return;
        }
        String at = getAt(method, typeStr);
        try {
            Class<?> klass = Class.forName(typeStr.split("<")[0]);
            if (!isContainer(klass)) {
                throw new RuntimeException("使用了未实现 List 或 Set 的容器" + at);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("使用了不支持或自定义的泛型类" + at);
        }
    }

    private boolean isContainer(Class<?> klass) {
        if (klass.equals(List.class) || klass.equals(Set.class)) {
            return true;
        }

        for (Class<?> anInterface : klass.getInterfaces()) {
            if (anInterface.equals(List.class) || anInterface.equals(Set.class)) {
                return true;
            } else {
                if (isContainer(anInterface)) {
                    return true;
                }
            }
        }
        return false;
    }

    private String getAt(ExecutableElement method, String param) {
        String at = "\n位于 Dao <" + parsingElement + "> 中的,";
        if (method != null) {
            at += "方法 <" + method + ">的";
        }
        if (param != null) {
            at += "参数 <" + param + ">";
        }
        return at;
    }

    public Map<ExecutableElement, AnnotationInfo> getAnnotatedMethods() {
        return method_AnnotationMap;
    }

    public String getSqlFormat(ExecutableElement method) {
        return querySqlFormatMap.get(method);
    }

    public String getEntity() {
        return entity;
    }

    public String getTable() {
        return table;
    }

    public String getEntityPackage() {
        return entityPackage;
    }


    public static Map<String, DaoClassParser> getDaoClassParserMap() {
        return daoClassParserMap;
    }
}
